---
order: 4
title: 动画状态机
type: 动画
label: Animation
---
## 简介

动画状态机（[API](/apis/core/#AnimatorStateMachine)）是一种用于控制和管理动画切换的工具，你可以用它添加各种动画状态及其之间的转换规则，使角色或动画对象能够在不同动作之间自然的切换。

### 主要组成部分

#### 动画状态
代表 `动画状态机` 中的一个单独状态，即某一时刻动画系统中播放的动画。，比如“站立”、“跑步”或“跳跃”。每个状态都有一个对应的 `动画片段` 。完整的API请参考[AnimatorState](/apis/core/#AnimatorState)。

| 属性 | 功能说明 |
| :------- | :------------------------------------------------------------------- |
| Name          | 修改 `AnimatorState` 的名字，名字在所在的层要是**唯一**的。           |
| AnimationClip | 用于绑定 `AnimationClip` 资产，`AnimationClip` 存储了模型的动画数据。 |
| WrapMode      | `AnimatorState` 是循环播放还是播放一次，默认为 `Once` 即播放一次。     |
| Speed         | `AnimatorState` 的播放速度，默认值为 1.0 ，值越大动画速度越快          |
| StartTime     | `AnimatorState` 从 `AnimationClip` 的哪个时间开始播放，时间为相对 `AnimationClip` 时长的归一化时间。默认值为 0 ，即从头开始播放。 例如：值为 1.0 ，则是 `AnimationClip` 的最后一帧状态。        |
| EndTime       | `AnimatorState` 播放到 `AnimationClip` 的哪个时间结束播放，时间为相对 `AnimationClip` 时长的归一化时间。默认值为 1.0 ，即播放到最后。                                                      |
| [StateMachineScripts](/apis/core/#StateMachineScript)| 允许开发者编写自定义的脚本逻辑，以在动画状态机的不同事件（如状态进入、退出、更新等）中执行特定的代码。它类似于 Script，但专门用于动画状态机。|

##### 在编辑器中有三个特殊的动画状态
`entry`： 用于表示动画状态机的进入点。进入动画状态机时，总是先进入 `entry`，然后根据定义的转换条件跳转到其他状态。 `entry` 本身不会播放动画，它主要用于连接状态机的起点到初始动画状态。通常情况，你应该将它连接到角色或动画对象的默认状态，比如角色的 `Idle（站立）` 状态。

`any`：允许动画状态机中任意状态发生特定条件时跳转到目标状态，这对于处理全局事件或紧急动画（如受伤、死亡）非常有用。

<Callout type="info">
`any` 状态具有最高的优先级，因此使用 `any` 需要谨慎，因为它可以打破当前动画的正常流转，可能会导致动画过渡不自然的情况。开发者需要确保只有在明确的条件下才使用 `any` 进行转换。
</Callout>

`exit`：表示动画状态机的退出点。当状态机进入 `exit` 时，通常意味着状态机结束。状态机将重新进入 `entry` 状态。


#### 动画过渡
用于定义 `动画状态机` 中两个 `动画状态` 之间的转换规则和条件。它决定了何时以及如何从一个 `动画状态` 切换到另一个 `动画状态` 。完整的API请参考[AnimatorStateTransition](/apis/core/#AnimatorStateTransition)。

| 属性 | 功能说明 |
| :------- | :---------------------------------------------------------|
| Duration | 过渡时长，时间为相对目标状态的归一化时间，默认值为 0.25            |
| Offset   | 目标状态向前的偏移时间，时间为相对目标状态的归一化时间，默认值为 0  |
| ExitTime | 起始状态过渡开始时间，时间为相对起始状态的归一化时间，默认值为 0.75  |
| Solo     | 使选定的动画过渡成为唯一活动的状态。其它动画过渡将被忽略。 |
| Mute     | 禁用选定的动画过渡 |
| Conditions | 过渡通过的条件，如果有多个条件，只有所有条件都满足时，才会开始过渡。 |

Solo 和 Mute 通常用于调试，用于帮助开发者更高效地测试和调试 `动画状态机` 。

## 使用动画状态机
### 默认播放

#### 编辑器使用
将 `动画状态` 连接到`entry`上你导出的项目运行时就会自动播放其上的动画，而不需再调用 `animator.play`。点击 `动画控制组件`（[详细介绍](/docs/animation/animator)）绑定的 `实体` ，即可预览动画。

<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*0_DkSoSAhmUAAAAAAAAAAAAADsJ_AQ/original" />

#### 脚本使用

你可以通过 [animatorStateMachine.addEntryStateTransition](/apis/core/#AnimatorStateMachine-addEntryStateTransition) 方法让 `动画状态` 连接到 `动画状态机` 的 `entry`

```typescript
const animatorStateMachine = animator.animatorController.layers[0].stateMachine;
animatorStateMachine.addEntryStateTransition('Idle');
```

### 动画过渡

#### 编辑器使用
将两个想要过渡的 `动画状态` 连接即可实现动画过渡的效果, 点击两个 `动画状态` 间的连线，可以修改动画过渡的参数调整效果。

<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*h3I4TZoxtnYAAAAAAAAAAAAADsJ_AQ/original" />

点击连线你可以设置 `动画过渡` 的参数，如添加条件：

<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*vlmLQqR2k7QAAAAAAAAAAAAADsJ_AQ/original" />

<Callout type="info">
  若添加多个条件，只有所有条件都满足时，才会开始过渡。
</Callout>

#### 脚本使用

你可以通过为 `动画状态` 添加 `动画过渡` 实现 `动画状态` 间切换的过渡效果。

```typescript
const walkState = animatorStateMachine.addState('walk');
walkState.clip = walkClip;
const runState = animatorStateMachine.addState('run');
runState.clip = runClip;
const transition = new AnimatorStateTransition();
transition.duration = 1;
transition.offset = 0;
transition.exitTime = 0.5;
transition.destinationState = runState;
walkState.addTransition(transition);
animator.play("walk");
```
通过这样的方式你之后每次在该 `动画状态机` 所在的 `动画层`（[详细介绍](/docs/animation/layer)） 播放 `walk` 动画时都会在播放一半时开始过渡到 `run` 动画。

##### 为动画过渡添加条件
你可以通过 [animatorStateTransition.addCondition](/apis/core/#AnimatorStateTransition-addCondition) 方法为 `动画过渡` 添加 `过渡条件`

```typescript
const idleState = animatorStateMachine.addState('idle');
idleState.clip = idleClip;
const walkState = animatorStateMachine.findStateByName('walk');
const transition = new AnimatorStateTransition();

transition.addCondition(AnimatorConditionMode.Greater, 'speed', 0);

transition.destinationState = walkState;
idleState.addTransition(transition);
```
通过这样的方式你之后每次在该 `动画状态机` 所在的层播放 `idle` 动画时并且 `speed` 参数大于 `0` 时，动画将过渡到 `walk` 动画。

##### 添加/删除动画参数
`动画参数` 用于控制 `动画状态机` 的 `动画状态` 切换，通过设置 `动画参数` 的值满足 `动画过渡` 的条件时，`动画过渡` 会触发，从而切换到目标 `动画状态`。

你可以通过 [animatorController.addParameter](/apis/core/#AnimatorController-addParametere) 方法来添加 `动画参数`，[animatorController.removeParameter](/apis/core/#AnimatorController-removeParametere) 方法来删除 `动画参数` 。

```typescript
const animatorController = animator.animatorController;
animatorController.addParameter('speed', 0);
// 删除参数
animatorController.removeParameter('speed');
```

##### 设置动画参数值
你可以通过 [animator.setParameterValue](/apis/core/#Animator-setParameterValue) 方法来设置 `动画参数` 的值

```typescript
class AnimationScriptExample extends Script {
  animator: Animator;
  onStart() {
    this.animator = this.entity.getComponent(Animator);
  }

  onUpdate(deltaTime: number) {
    const inputManager = this.engine.inputManager;

    // 如果用户按下 W 键，则设置 speed 参数值为 1, 符合切换到走路状态的条件，动画切换到走路
    if (inputManager.isKeyHeldDown(Keys.KeyW)) {
      this.animator.setParameterValue('speed', 1);
    }
    // 如果用户松开 W 键，则设置 speed 参数值为 0, 符合切换到站立状态的条件，动画切换到站立
    if (inputManager.isKeyUp(Keys.KeyW)) {
      this.animator.setParameterValue('speed', 0);
    }
  }
}
```

##### 获取动画参数值
你可以通过 [animator.getParameterValue](/apis/core/#Animator-getParameterValue) 方法来获取 `动画参数` 的值：
```typescript
class AnimationScriptExample extends Script {
  animator: Animator;

  onStart() {
    this.animator = this.entity.getComponent(Animator);
  }

  onUpdate(deltaTime: number) {
    const speed = this.animator.getParameterValue('speed');
    if (speed === 1) {
      console.log("The player is walking")
    }
  }
}
```

##### 获取动画参数对象
你可以通过 [animator.getParameter](/apis/core/#Animator-getParameter) 方法来获取 `动画参数` 对象：

```typescript
class AnimationScriptExample extends Script {
  animator: Animator;

  onStart() {
    this.animator = this.entity.getComponent(Animator);
    const speedParameter = this.animator.getParameter('speed');
    // 设置 speed 的默认值
    speedParameter.defaultValue = 0;
  }
}
```

### 动画状态机动画循环
将 `动画状态` 连接到 `exit` 状态，`动画状态机` 会退出并重新进入 `entry`，使得整体流程循环
<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*1AfgS6S88SYAAAAAAAAAAAAADsJ_AQ/original" /> 


#### 脚本使用

你可以通过 [state.addExitTransition](/apis/core/#AnimatorState-addExitTransition) 方法让 `动画状态` 连接到 `动画状态机` 的 `exit`

```typescript
const runState = animatorStateMachine.addState('run');
runState.addExitTransition();
```
通过这样的方式你之后每次在该 `动画状态机` 所在的 `动画层` 播放 `Run` 动画时都会播放结束就会进入 `exit` 状态之后重新从 `entry` 开始。



### 状态机脚本
你可以为每个 `动画状态` 添加 `状态机脚本` ，它可以让你在 `动画状态机` 的不同事件（如状态进入、退出、更新等）中接收到回调[详见](/apis/core/#StateMachineScript)。

<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*VrCXQqS3w7QAAAAAAAAAAAAADsJ_AQ/original" />

<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*A7I7QqiDCfkAAAAAAAAAAAAADsJ_AQ/original" />

#### 脚本使用
`状态机脚本` 提供了三个动画状态周期：

- `onStateEnter`：动画状态开始播放时回调。
- `onStateUpdate`：动画状态更新时回调。
- `onStateExit`：动画状态结束时回调。

```typescript
class theScript extends StateMachineScript {
  /**
   * onStateEnter is called when a transition starts and the state machine starts to evaluate this state.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateEnter(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Enter ${animatorState.name}`)
  }

  /**
   * onStateUpdate is called on each Update frame between onStateEnter and onStateExit callbacks.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateUpdate(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Update ${animatorState.name}`)
  }

  /**
   * onStateExit is called when a transition ends and the state machine finishes evaluating this state.
   * @param animator - The animator
   * @param animatorState - The state be evaluated
   * @param layerIndex - The index of the layer where the state is located
   */
  onStateExit(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {
    console.log(`Exit ${animatorState.name}`)
  }
}

animatorState.addStateMachineScript(theScript)
```


如果你的脚本不用复用的话你也可以这么写:

```typescript
state.addStateMachineScript(
  class extends StateMachineScript {
    onStateEnter(
      animator: Animator,
      animatorState: AnimatorState,
      layerIndex: number
    ): void {
      console.log("onStateEnter: ", animatorState);
    }
  }
);
```

## 多层动画状态机混合
每个 `动画层` 都有一个 `动画状态机` ，动画最终的表现由多个 `动画层` 进行混合而成，如何使用 `动画层` 可以参考[动画层](/docs/animation/layer)文档。
